//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
//--------------------------------------------------------------------------

//    A Flash Translation Layer memory card driver
//
//    This driver implements a disk-like block device driver with an
//    apparent block size of 512 bytes for flash memory cards.
//
//======================================================================

#include "ddk.h"
#include "ftl.h"

const int shuffle_freq = 5; // for wear leveling

static void * FtlKmalloc(UInt32 n)
{
    void *temp = NULL;
	UInt32 num = n + sizeof(UInt32);
    UInt32 order = 0;
    if (num <= PAGE_SIZE-4) {
		temp = malloc(num);
    }
	else {
        order = GET_UP_ORDER(num/PAGE_SIZE);
        /*
         * c_uNumberOfOrders is up_limit for AllocKernelPages
         */
        if (order >= c_uNumberOfOrders) {
            return NULL;
        }
		temp = DzAllocKernelPages(order);
	}

	if (!temp) {
		return NULL;
	}
	else {
		*((UInt32 *)temp) = num;
		return (UInt32 *)temp + 1;
	}
}

static void FtlKfree(void *add)
{
	UInt32 num = 0;
	assert(add != NULL);
	num = *((UInt32 *)add - 1);

	if (num <= PAGE_SIZE-4) {
		free((UInt32 *)add - 1);
	}
	else {
		DzFreeKernelPages((UInt32 *)add - 1, GET_UP_ORDER(num/PAGE_SIZE));
	}
}

//#define _FTLWRITETEST
#ifdef _FTLWRITETEST
static int write_number = 0;
static int reclaim_number = 0;
#define WRITETEST_MAX 0x10000
#endif

int ReadFlash(
        PDRIVER pDriver,
        UInt64 offset,
        Byte *pBuffer,
        UInt32 uSize,
        UInt32 *pRetSize)
{
    MemoryBuf ebbData((Byte *)pBuffer, uSize);
    ECode ec = pDriver->Read(offset, uSize, &ebbData, NULL);
    if (FAILED(ec)) {
        printf("*ERROR* failed to read flash\n");
        return -1;
    }

    *pRetSize = ebbData.GetUsed();

    return 0;
}

int WriteFlash(
        PDRIVER pDriver,
        UInt64 offset,
        Byte *pBuffer,
        UInt32 uSize,
        UInt32 *pRetSize)
{
    MemoryBuf ebbData(pBuffer, uSize);

    ECode ec = pDriver->Write((Int64)offset, ebbData, (Int32*)pRetSize, NULL);

    if (FAILED(ec)) {
        printf("*ERROR* failed to write flash\n");
        return -1;
    }

    return 0;
}


int EraseFlash(
        PDRIVER pDriver,
        UInt32 offset)
{
    MemoryBuf_<sizeof(UInt32)> ebbInData;
    *(UInt32 *)ebbInData.GetPayload() = offset;
    ebbInData.SetUsed(sizeof(UInt32));
    ECode ec = pDriver->Control(
            FlashControl_EraseSector,
            ebbInData,
            NULL,
            NULL);
    if (FAILED(ec)) {
        printf("*ERROR* failed to erase flash sector\n");
        return -1;
    }

    return 0;
}

int GetFlashInfo(
        PDRIVER pDriver,
        UInt32 *pSize,
        UInt32 *pErasesize)
{
    MemoryBuf_<sizeof(FlashInfo)> ebbOutData;
    ECode ec = pDriver->Control(
            FlashControl_GetInfo,
            NULL_MEMORYBUF,
            &ebbOutData,
            NULL);
    if (FAILED(ec)) {
        printf("*ERROR* failed to get flash info\n");
        return -1;
    }

    FlashInfo *pInfo;
    pInfo = (FlashInfo *)ebbOutData.GetPayload();
    *pSize = pInfo->Size;
    *pErasesize = pInfo->EraseUnitSize;
    return 0;
}

/*======================================================================

  Scan_header() checks to see if a memory region contains an FTL
  partition.  build_maps() reads all the erase unit headers, builds
  the erase unit map, and then builds the virtual page map.

======================================================================*/
int scan_header(partition_t *part)
{
    erase_unit_header_t header;
    UInt32 offset, max_offset;
    int ret;

    DBGOUT(DBGFS,0,printf("Begin scan_header\n"));

    max_offset = ( 0x100000 <part->size )?0x100000:part->size;
    /* Search first megabyte for a valid FTL header */
    for (offset = 0; offset < max_offset; offset += part->erasesize?part->erasesize:0x2000) {
        ret = ReadFlash(part->mtd, offset, (unsigned char *)&header, sizeof(header), (unsigned int *)&ret);
        if (ret)
            return ret;

        if (strcmp((const char *)(header.DataOrgTuple+3), "FTL100") == 0) break;
    }
    if (offset == max_offset) {
        DBGOUT(DBGFS,2,printf("FTL: FTL header not found.\n"));
        return -1;
    }
    else {
        DBGOUT(DBGFS,1,printf("FTL header is OK!\n"));
    }
    if (/* (header.NumEraseUnits > 65536) || */ header.BlockSize != 9 ||
        (header.EraseUnitSize < 10) || (header.EraseUnitSize > 31) ||
        (header.NumTransferUnits >= header.NumEraseUnits)) {
        DBGOUT(DBGFS,0,printf("FTL: FTL header corrupt!\n"));
        return -1;
    }
    if (((UInt32)1 << header.EraseUnitSize) != part->erasesize) {
        DBGOUT(DBGFS,0,printf("ftl: FTL EraseUnitSize %x, MTD erasesize %x\n",
            1 << header.EraseUnitSize, part->erasesize));
        return -1;
    }
    // Set header struct
    part->header = header;

    DBGOUT(DBGFS,0,printf("End scan_header\n"));
    return 0;
}

int build_maps(partition_t *part)
{
    erase_unit_header_t header;
    UInt16 xvalid, xtrans, i;
    UInt32 blocks, j;
    int hdr_ok, ret;
    UInt32 retval;
    UInt32 offset;

    DBGOUT(DBGFS,0,printf("Begin build_maps\n"));
    /* Set up erase unit maps */
    part->DataUnits = part->header.NumEraseUnits -
        part->header.NumTransferUnits;
    part->EUNInfo = (struct eun_info_t *)FtlKmalloc(part->DataUnits * sizeof(struct eun_info_t) );
    if (!part->EUNInfo) return -1;

    for (i = 0; i < part->DataUnits; i++)
        part->EUNInfo[i].Offset = 0xffffffff;

    part->XferInfo = (struct xfer_info_t *)FtlKmalloc(part->header.NumTransferUnits * sizeof(struct xfer_info_t) );
    if (!part->XferInfo) return -1;
    xvalid = xtrans = 0;
    for (i = 0; i < part->header.NumEraseUnits; i++) {
        offset = ((i + part->header.FirstPhysicalEUN)
            << part->header.EraseUnitSize);

        ret = ReadFlash(part->mtd, offset, (unsigned char *)&header, sizeof(header), &retval);

        if (ret)
            return ret;

        /* Is this a transfer partition? */
        hdr_ok = (strcmp((const char *)(header.DataOrgTuple+3), "FTL100") == 0);
        if (hdr_ok && (header.LogicalEUN < part->DataUnits) &&
            (part->EUNInfo[header.LogicalEUN].Offset == 0xffffffff)) {
            part->EUNInfo[header.LogicalEUN].EraseCount = header.EraseCount;
            part->EUNInfo[header.LogicalEUN].Offset = offset;
            xvalid++;
        } else {

            if (xtrans == part->header.NumTransferUnits) {
                DBGOUT(DBGFS,2,printf("FTL: format error: too many transfer units!\n"));
                DBGOUT(DBGFS,0,printf("xvalid=0x%x, xtrans=0x%x\n", xvalid,xtrans));
                return -1;
            }
            if (hdr_ok && (header.LogicalEUN == 0xffff)) {
                part->XferInfo[xtrans].state = XFER_PREPARED;
                part->XferInfo[xtrans].EraseCount = header.EraseCount;
            } else {
                part->XferInfo[xtrans].state = XFER_UNKNOWN;
                /* Pick anything reasonable for the erase count */
                part->XferInfo[xtrans].EraseCount =
                    part->header.EraseCount;
            }
            part->XferInfo[xtrans].Offset = offset;
            xtrans++;
        }
    }
    DBGOUT(DBGFS,0,printf("xvalid=0x%x, xtrans=0x%x\n", xvalid,xtrans));
    /* Check for format trouble */
    header = part->header;
    if ((xtrans != header.NumTransferUnits) ||
        (xvalid+xtrans != header.NumEraseUnits)) {
        DBGOUT(DBGFS,2,printf("FTL: format error: erase units don't add up!\n"));
        return -1;
    }
    /* Set up virtual page map */
    blocks = header.FormattedSize >> header.BlockSize;

    part->VirtualBlockMap = (unsigned int *)FtlKmalloc(blocks * sizeof(UInt32));
    if (!part->VirtualBlockMap) return -1;

    memset(part->VirtualBlockMap, 0xff, blocks * sizeof(UInt32));
    part->BlocksPerUnit = (1 << header.EraseUnitSize) >> header.BlockSize;
    part->bam_cache = (unsigned int *)malloc(part->BlocksPerUnit * sizeof(UInt32));
    if (!part->bam_cache) return -1;

    part->bam_index = 0xffff;
    part->FreeTotal = 0;

    for (i = 0; i < part->DataUnits; i++) {
        part->EUNInfo[i].Free = 0;
        part->EUNInfo[i].Deleted = 0;
        offset = part->EUNInfo[i].Offset + header.BAMOffset;

        ret = ReadFlash(part->mtd, offset, (unsigned char *)part->bam_cache,
            part->BlocksPerUnit * sizeof(UInt32), &retval);

        if (ret)
            return ret;

        for (j = 0; j < part->BlocksPerUnit; j++) {
            if (BLOCK_FREE(part->bam_cache[j])) {
                part->EUNInfo[i].Free++;
                part->FreeTotal++;
            } else if ((BLOCK_TYPE(part->bam_cache[j]) == BLOCK_DATA) &&
                (BLOCK_NUMBER(part->bam_cache[j]) < blocks))
                part->VirtualBlockMap[BLOCK_NUMBER(part->bam_cache[j])] =
                (i << header.EraseUnitSize) + (j << header.BlockSize);
            else if (BLOCK_DELETED(part->bam_cache[j]))
                part->EUNInfo[i].Deleted++;
        }
    }

    DBGOUT(DBGFS,1,printf("End build_maps, Valid FTL partition found.\n"));

    return 0;

}

//======================================================================
//
//    Erase_xfer() perform an synchronous erase operation for a
//    transfer unit.
//
//======================================================================

int erase_xfer(partition_t *part, UInt16 xfernum)
{
    int ret;
    struct xfer_info_t *xfer;

    DBGOUT(DBGFS,0,printf("Begin erase_xfer %x\n", xfernum));

    xfer = &part->XferInfo[xfernum];
    DBGOUT(DBGFS,0,printf("FTL: erasing xfer unit at %x\n", xfer->Offset));
    xfer->state = XFER_ERASING;

    ret = EraseFlash(part->mtd, xfer->Offset);

    if (!ret) {
        xfer->state = XFER_ERASED;
        xfer->EraseCount++;
    }
    else {
        xfer->state = XFER_FAILED;
        DBGOUT(DBGFS,0,printf("ftl: erase failed: state %x\n ",xfer->state));
    }

    return ret;
} // erase_xfer

//======================================================================
//
//    Prepare_xfer() takes a freshly erased transfer unit and gives
//    it an appropriate header.
//
//======================================================================

static void prepare_xfer(partition_t *part, int xfernum)
{
    erase_unit_header_t header;
    struct xfer_info_t *xfer;
    int nbam, ret;
    UInt32 ctl;
    UInt32 retlen;
    UInt32 offset;
    int i;
    DBGOUT(DBGFS,0,printf("Begin prepare_xfer: %x\n",xfernum));

    xfer = &part->XferInfo[xfernum];
    xfer->state = XFER_FAILED;

    DBGOUT(DBGFS,0,printf("FTL: preparing xfer unit at %x\n", xfer->Offset));

    // Write the transfer unit header
    header = part->header;
    header.LogicalEUN = 0xffff;
    header.EraseCount = xfer->EraseCount;
    offset = xfer->Offset;

    ret = WriteFlash(part->mtd, (UInt64)xfer->Offset, (UInt8 *)&header, sizeof(header), &retlen);

    if (ret){
        DBGOUT(DBGFS,0,printf("part->mtd->WriteAt() failed in prepare_xfer!\n"));
        return;
    }

    // Write the BAM stub
    nbam = (part->BlocksPerUnit * sizeof(UInt32) +
        part->header.BAMOffset + SECSZ - 1) / SECSZ;
    ctl = BLOCK_CONTROL;
    offset += part->header.BAMOffset;

    for (i = 0; i < nbam; i++, offset += sizeof(UInt32)) {
        ret = WriteFlash(part->mtd, (UInt64)offset, (UInt8 *)&ctl, sizeof(UInt32), \
            &retlen);
        if (ret){
            DBGOUT(DBGFS,0,printf("part->mtd->WriteAt() failed in prepare_xfer!\n"));
            return;
        }
        xfer->state = XFER_PREPARED;

    } // prepare_xfer
}

/*======================================================================

    Copy_erase_unit() takes a full erase block and a transfer unit,
    copies everything to the transfer unit, then swaps the block
    pointers.

    All data blocks are copied to the corresponding blocks in the
    target unit, so the virtual block map does not need to be
    updated.

======================================================================*/

int copy_erase_unit(partition_t *part, UInt16 srcnum,
			   UInt16 xfernum)
{
#define OFFSET_LogicalEUN 20
    UInt8 buf[SECSZ];
    struct eun_info_t *eun;
    struct xfer_info_t *xfer;
    UInt32 src, dest, free, i;
    UInt16 unit;
    int ret;
    int retlen;
    UInt32 offset;
    UInt32 srcunitswap = (UInt32)srcnum;
    DBGOUT(DBGFS,0,printf("Begin copy_erase_uint srcnum %x, xfernum %x\n",srcnum,xfernum));

    eun = &part->EUNInfo[srcnum];
    xfer = &part->XferInfo[xfernum];
    DBGOUT(DBGFS,0,printf("FTL: copying block %x to %x\n", eun->Offset, xfer->Offset));
    /* Read current BAM */
    if (part->bam_index != srcnum) {

	    offset = eun->Offset + part->header.BAMOffset;

        ret = ReadFlash(part->mtd, offset, (UInt8 *) (part->bam_cache),
			          part->BlocksPerUnit * sizeof(UInt32), (unsigned int *)&retlen);

	    /* mark the cache bad, in case we get an error later */
	    part->bam_index = 0xffff;

	    if (ret) {
	        DBGOUT(DBGFS,0,printf("ftl: Failed to read BAM cache in copy_erase_unit()!\n"));
	        return ret;
	    }
    }

    /* Write the LogicalEUN for the transfer unit */
    xfer->state = XFER_UNKNOWN;
    offset = xfer->Offset + OFFSET_LogicalEUN; /* Bad! it is LogicalEUN */
    unit = 0x7fff;
    ret = WriteFlash(part->mtd, (UInt64)offset, (UInt8 *) &unit, sizeof(UInt16),
			   (unsigned int *)&retlen);
    if (ret) {
	DBGOUT(DBGFS,0,printf("ftl: Failed to write back to BAM cache in copy_erase_unit()!\n"));
	return ret;
    }

    /* Copy all data blocks from source unit to transfer unit */
    src = eun->Offset; dest = xfer->Offset;

    free = 0;
    ret = 0;
    for (i = 0; i < part->BlocksPerUnit; i++) {
	switch (BLOCK_TYPE(part->bam_cache[i])) {
	case BLOCK_CONTROL:
	    /* This gets updated later */
	    break;
	case BLOCK_DATA:
	case BLOCK_REPLACEMENT:
        ret = ReadFlash(part->mtd, src, (UInt8 *) buf, SECSZ,
                        (unsigned int *)&retlen);
	    if (ret) {
		DBGOUT(DBGFS,0,printf("ftl: Error reading old xfer unit in copy_erase_unit\n"));
		return ret;
            }
	    ret = WriteFlash(part->mtd, (UInt64)dest, (UInt8 *) buf, SECSZ,
                        (unsigned int *)&retlen);
	    if (ret)  {
		DBGOUT(DBGFS,0,printf("ftl: Error writing new xfer unit in copy_erase_unit\n"));
		return ret;
            }

	    break;
	default:
	    /* All other blocks must be free */
	    part->bam_cache[i] = 0xffffffff;
	    free++;
	    break;
	}
	src += SECSZ;
	dest += SECSZ;
    }
    /* Write the BAM to the transfer unit */
    ret = WriteFlash(part->mtd, (UInt64)xfer->Offset + part->header.BAMOffset, (UInt8 *)part->bam_cache,
                    part->BlocksPerUnit * sizeof(int), (unsigned int *)&retlen);
    if (ret) {
	DBGOUT(DBGFS,0,printf("ftl: Error writing BAM in copy_erase_unit\n"));
	return ret;
    }

    /* All clear? Then update the LogicalEUN again */
    ret = WriteFlash(part->mtd, (UInt64)xfer->Offset + OFFSET_LogicalEUN, (UInt8 *)&srcunitswap, sizeof(UInt16),
			   (unsigned int *)&retlen);
    if (ret) {
	DBGOUT(DBGFS,0,printf("ftl: Error writing new LogicalEUN in copy_erase_unit\n"));
	return ret;
    }


    /* Update the maps and usage stats*/
    i = xfer->EraseCount;
    xfer->EraseCount = eun->EraseCount;
    eun->EraseCount = i;
    i = xfer->Offset;
    xfer->Offset = eun->Offset;
    eun->Offset = i;
    part->FreeTotal -= eun->Free;
    part->FreeTotal += free;
    eun->Free = free;
    eun->Deleted = 0;

    /* Now, the cache should be valid for the new block */
    part->bam_index = srcnum;


    return 0;
} /* copy_erase_unit */

/*======================================================================

    reclaim_block() picks a full erase unit and a transfer unit and
    then calls copy_erase_unit() to copy one to the other.  Then, it
    schedules an erase on the expired block.

    What's a good way to decide which transfer unit and which erase
    unit to use?  Beats me.  My way is to always pick the transfer
    unit with the fewest erases, and usually pick the data unit with
    the most deleted blocks.  But with a small probability, pick the
    oldest data unit instead.  This means that we generally postpone
    the next reclaimation as long as possible, but shuffle static
    stuff around a bit for wear leveling.

======================================================================*/
int sync()
{
    // Add DzSleep(0) to avoid warning: statement has no effect
    DzSleep(0, NULL);
    return 0;
}

/* Pick the least erased transfer unit */
int find_xfer(partition_t *part)
{
    UInt16 i, xfer;
    UInt32 best;

    best = 0xffffffff; xfer = 0xffff;
    do {
	    for (i = 0; i < part->header.NumTransferUnits; i++) {
	        int n=0;
	        if (part->XferInfo[i].state == XFER_UNKNOWN) {
		        DBGOUT(DBGFS,0,printf("XferInfo[%d].state == XFER_UNKNOWN\n",i));
		        n=1;
		        erase_xfer(part, i);
	        }
            /*
             * XFER_ERASING : representing that the last erase_xfer routine
             * did not perform successfully, Maybe it was been interrupted,
             * we should call the routine again.
             */
	        if (part->XferInfo[i].state == XFER_ERASING) {
		        DBGOUT(DBGFS,0,printf("XferInfo[%d].state == XFER_ERASING\n",i));
		        n=1;
                erase_xfer(part, i);
	        }
	        if (part->XferInfo[i].state == XFER_ERASED) {
		        DBGOUT(DBGFS,0,printf("XferInfo[%d].state == XFER_ERASED\n",i));
		        n=1;
		        prepare_xfer(part, i);
	        }
	        if (part->XferInfo[i].state == XFER_PREPARED) {
		        DBGOUT(DBGFS,0,printf("XferInfo[%d].state == XFER_PREPARED\n",i));
		        n=1;
		        if (part->XferInfo[i].EraseCount < best) {
		            best = part->XferInfo[i].EraseCount;
		            xfer = i;
		        }
	        }
            if (!n) {
		        DBGOUT(DBGFS,0,printf("XferInfo[%d].state == %x\n", part->XferInfo[i].state));
            }

	    }
	    if (xfer == 0xffff) {
		    static int ne = 0;
            if (++ne < 5) {
		        DBGOUT(DBGFS,0,printf("FTL: reclaim failed: no suitable transfer units!\n"));
            }
            else {
		        DBGOUT(DBGFS,0,printf("FTL: reclaim failed: no suitable transfer units!\n"));
            }

		    return -1;
	    }
    } while (xfer == 0xffff);
    return xfer;
}

int reclaim_block(partition_t *part)
{
    UInt16 i, eun, xfer;
    UInt32 best;
    int ret;

    DBGOUT(DBGFS,1,printf("FTL: reclaiming space...\n"));

#ifdef _FTLWRITETEST
    reclaim_number++;
#endif

    xfer = find_xfer(part);

    eun = 0;

    static int jiffies = 0;
    if ((++jiffies) == shuffle_freq) {
	    DBGOUT(DBGFS,0,printf("FTL: recycling freshest block...\n"));
	    jiffies = 0;
	    best = 0xffffffff;
	    for (i = 0; i < part->DataUnits; i++)
	        if (part->EUNInfo[i].Deleted > 0 && part->EUNInfo[i].EraseCount < best) {
		        best = part->EUNInfo[i].EraseCount;
		        eun = i;
	        }
    } else {
	    best = 0;
	    for (i = 0; i < part->DataUnits; i++)
	        if (part->EUNInfo[i].Deleted > best) {
		        best = part->EUNInfo[i].Deleted;
		        eun = i;
            }
	    if (best == 0) {
	        static int ne = 0;
            if (++ne < 5) {
		        DBGOUT(DBGFS,0,printf("FTL: reclaim failed: no free blocks!\n"));
    	        return -1;
            }
        }
    }
    ret = copy_erase_unit(part, eun, xfer);
    if (!ret)
    	erase_xfer(part, xfer);
    else
	    DBGOUT(DBGFS,0,printf("FTL: copy_erase_unit failed!\n"));
    return ret;
} /* reclaim_block */

//======================================================================
//
//    Find_free() searches for a free block.  If necessary, it updates
//    the BAM cache for the erase unit containing the free block.  It
//    returns the block index -- the erase unit is just the currently
//    cached unit.  If there are no free blocks, it returns 0 -- this
//    is never a valid data block because it contains the header.
//
//======================================================================
#ifdef _DEBUG
static void dump_lists(partition_t *part)
{
    int i;
    DBGOUT(DBGFS,0,printf("FTL: Free total %x\n", part->FreeTotal));
    for (i = 0; i < part->DataUnits; i++) {
        DBGOUT(DBGFS,0,printf("FTL:  %d  unit %x phys %x free %x deleted %x\n", i,
            part->EUNInfo[i].Offset >> part->header.EraseUnitSize,
            part->EUNInfo[i].Free, part->EUNInfo[i].Deleted));
    }
}
#endif

UInt32 find_free(partition_t *part)
{
    UInt16 stop, eun;
    UInt32 blk;
    UInt32 retlen;
    int ret;

    // Find an erase unit with some free space
    stop = (part->bam_index == 0xffff) ? 0 : part->bam_index;

    eun = stop;
    do {
        if (part->EUNInfo[eun].Free != 0)
            break;
        // Wrap around at end of table
        if (++eun == part->DataUnits)
            eun = 0;
    } while (eun != stop);

    if (part->EUNInfo[eun].Free == 0)
        return 0;

    // Is this unit's BAM cached?
    if (eun != part->bam_index) {
        // Invalidate cache
        part->bam_index = 0xffff;

        ret = ReadFlash(part->mtd, part->EUNInfo[eun].Offset + part->header.BAMOffset, (UInt8 *) (part->bam_cache),
            part->BlocksPerUnit * sizeof(UInt32),
            &retlen);

        if (ret) {
            DBGOUT(DBGFS,0,printf("ftl: Error reading BAM in find_free\n"));
            return 0;
        }

        part->bam_index = eun;
    }

    // Find a free block
    for (blk = 0; blk < part->BlocksPerUnit; blk++)
        if (BLOCK_FREE(part->bam_cache[blk])) break;

        if (blk == part->BlocksPerUnit) {
#ifdef _DEBUG
            static int ne = 0;
            if (++ne == 1)
                dump_lists(part);
#endif
            DBGOUT(DBGFS,0,printf("FTL: bad free list!\n"));
            return 0;
        }

        DBGOUT(DBGFS,0,printf("FTL: found free block at %ld in %d\n", blk, eun));
        return blk;

} // find_free

//======================================================================
//
//    This gets a memory handle for the region corresponding to the
//    minor device number.
//
//======================================================================


//====================================================================

EXTERN_C int ftl_close(partition_t *part)
{
    int i;

     /* Wait for any pending erase operations to complete */
    sync();

    // Wait for any pending erase operations to complete
    for (i = 0; i < part->header.NumTransferUnits; i++)
    {
        if (part->XferInfo[i].state == XFER_ERASED)
            prepare_xfer(part, i);
    }

	part->open--;

    DBGOUT(DBGFS,0,printf("Ready to free stuff\n"));

    return 0;
}

int set_bam_entry(partition_t *part, UInt32 log_addr,
			 UInt32 virt_addr)
{
    UInt32 bsize, blk, le_virt_addr;
#ifdef PSYCHO_DEBUG
    UInt32 old_addr;
#endif
    UInt16 eun;
    int ret;
    UInt32 retlen, offset;

    DBGOUT(DBGFS,0,printf("FTL: set_bam_entry: log_addr=%x, virt_addr=%x\n",log_addr, virt_addr));
    bsize = 1 << part->header.EraseUnitSize;
    eun = log_addr / bsize;
    blk = (log_addr % bsize) / SECSZ;
    offset = (part->EUNInfo[eun].Offset + blk * sizeof(UInt32) +
		  part->header.BAMOffset);

#ifdef PSYCHO_DEBUG
    ret = ReadFlash(part->mtd, offset, (UInt8 *)&old_addr, sizeof(UInt32),
                        &retlen);
    if (ret) {
	DBGOUT(DBGFS,0,printf("ftl: Error reading old_addr in set_bam_entry: ret=%x\n", ret));;
	return ret;
    }

    if (((virt_addr == 0xfffffffe) && !BLOCK_FREE(old_addr)) ||
	((virt_addr == 0) && (BLOCK_TYPE(old_addr) != BLOCK_DATA)) ||
	(!BLOCK_DELETED(virt_addr) && (old_addr != 0xfffffffe))) {
	static int ne = 0;
	if (++ne < 5) {
	    DBGOUT(DBGFS,0,printf("FTL: set_bam_entry() inconsistency! log_addr %x, old %x, new %x\n",
            log_addr, old_addr, virt_addr));
	}
	return -EIO;
    }
#endif
    le_virt_addr = virt_addr;
    if (part->bam_index == eun) {
#ifdef PSYCHO_DEBUG
	if (part->bam_cache[blk] != old_addr) {
	    static int ne = 0;
	    if (++ne < 5) {
		DBGOUT(DBGFS,0,printf("FTL: set_bam_entry() inconsistency! log_addr=0x%x, cache=0x%x\n",
		       part->bam_cache[blk], old_addr));
	    }
	    return -EIO;
	}
#endif
	part->bam_cache[blk] = le_virt_addr;
    }
    ret = WriteFlash(part->mtd, (UInt64)offset, (UInt8 *)&le_virt_addr, sizeof(UInt32),
                            &retlen);

    if (ret) {
	DBGOUT(DBGFS,0,printf("FTL: set_bam_entry() failed!log_addr %x, new %x\n", log_addr, virt_addr));
    }
    return ret;
} /* set_bam_entry */

EXTERN_C void ftl_freepart(partition_t *part)
{
    DBGOUT(DBGFS,0,printf("Begin ftl_freepart\n"));
    if (part->VirtualBlockMap) {
        FtlKfree(part->VirtualBlockMap);
        part->VirtualBlockMap = NULL;
    }
    if (part->EUNInfo) {
        FtlKfree(part->EUNInfo);
        part->EUNInfo = NULL;
    }
    if (part->XferInfo) {
        FtlKfree(part->XferInfo);
        part->XferInfo = NULL;
    }
    if (part->bam_cache) {
        free(part->bam_cache);
        part->bam_cache = NULL;
    }
}

void TestFtl( partition_t *part)
{
    printf("Begin test ftl, part=%x\n", part);
    scan_header(part);
    print_header(&part->header);
    reclaim_block(part);
//    Chat();
    ftl_close(part);
//    Chat();
    printf("stop here\n");for(;;); // stop here
}

EXTERN_C int ftl_init(partition_t *part,PDRIVER mtd)
{
//    UInt64 size;
    DBGOUT(DBGFS,0,printf("Begin ftl_init part %x, mtd %x\n", part, mtd));

    if (!mtd)
    {
        // Removing device
        if (part->state == FTL_FORMATTED)
            ftl_freepart(part);

        part->state = 0;
        return -1;
    }else{
        // Adding device
        if (part->state == FTL_FORMATTED)
            return -1;
        part->state = 0;
        part->mtd   = mtd;
        // get flash size and erasesize
        GetFlashInfo(mtd, &part->size, &part->erasesize);

        if ((scan_header(part) == 0) &&
	        (build_maps(part) == 0)) {
		    part->open = 0;
            part->state = FTL_FORMATTED;
            DBGOUT(DBGFS,2,printf("FTL: opening %dkb FTL partition\n", part->header.FormattedSize >> 10));
        }
        else{
            ftl_freepart(part);
            print_part(part);
            return -1 ;
        }
    }
    DBGOUT(DBGFS,0,printf("End ftl_init\n"));
    return 0;
}

EXTERN_C int ftl_delete(partition_t *part, UInt32 sector, UInt32 nblocks)
{
    UInt32 bsize, log_addr, virt_addr;
    UInt32 i;

    bsize = 1 << part->header.EraseUnitSize;

    virt_addr = sector * SECSZ;
    for (i = 0; i < nblocks; i++, virt_addr += SECSZ) {
        if (virt_addr >= part->header.FormattedSize) {
	        DBGOUT(DBGFS,0,printf("Ftl: bad delete offset\n"));
	        return -1;
	    }
    	log_addr = part->VirtualBlockMap[sector+i];
    	if (log_addr != 0xffffffff) {
    	    part->VirtualBlockMap[sector+i] = 0xffffffff;
    	    part->EUNInfo[log_addr/bsize].Deleted++;
    	    if (set_bam_entry(part, log_addr, 0))
                return -1;
       }

    }

    return 0;
}

///-------------------------------------------------------------------------///
//	Reads and writes to the hard disk
//----------------------------------------------------------------------------
//
//======================================================================
//
//    Read a series of sectors from an FTL partition.
//
//======================================================================*/

EXTERN_C int ftl_read(partition_t *part, Byte * buffer,
		    UInt32 sector, UInt32 nblocks)
{
    UInt32 log_addr, bsize;
    UInt32 i;
    int ret;
    UInt32 offset, retlen;

    DBGOUT(DBGFS,0,printf("Ftl: ftl_read buffer %x, sector %x, nblocks %x\n", (UInt32)buffer, sector ,nblocks));
    if (!(part->state & FTL_FORMATTED)) {
	    DBGOUT(DBGFS,0,printf("Ftl: bad partition\n"));
	    return -1;
    }

    if (((sector+nblocks-1) * SECSZ) >= part->header.FormattedSize) {
    	DBGOUT(DBGFS,0,printf("Ftl: bad read offset\n"));
    	return -1;
    }

    bsize = 1 << part->header.EraseUnitSize;

    for (i = 0; i < nblocks; i++) {
    	log_addr = part->VirtualBlockMap[sector+i];
    	if (log_addr == 0xffffffff)
    	    memset(buffer, 0, SECSZ);
    	else {
    	    offset = (part->EUNInfo[log_addr / bsize].Offset
    			  + (log_addr % bsize));
            ret = ReadFlash(part->mtd, offset, (UInt8 *) buffer, SECSZ,
    			   &retlen);

    	    if (ret) {
        	    DBGOUT(DBGFS,0,printf("Error reading MTD device in ftl_read()\n"));
        	    return ret;
    	    }
    	}
    	buffer += SECSZ;
    }
    return 0;
} /* ftl_read */


EXTERN_C int ftl_write(partition_t *part, Byte * buffer,
		     UInt32 sector, UInt32 nblocks)
{
    UInt32 bsize, log_addr, virt_addr, old_addr, blk;
    UInt32 i;
    int ret;
    UInt32 retlen, offset;

#ifdef _FTLWRITETEST
    write_number++;
    if ((write_number & 0xff) == 0) printf("*");
    if (write_number == WRITETEST_MAX ) {
        printf("\nAfter test: \nwrite_number = %x,\nreclaim_number = %x\n",
            write_number, reclaim_number);
        for (int j = 0; j < part->DataUnits; j++) {
            printf("Unit %x, EraseCount = %x\n", j, part->EUNInfo[j].EraseCount);
        }
    }
#endif

    if (sector <= 100 && nblocks == 1) {
        Byte tmpbuf[SECSZ];

        ftl_read(part, tmpbuf, sector, nblocks);

        if ( memcmp(buffer,tmpbuf,SECSZ) == 0 ) {
            DBGOUT(DBGFS,1,printf("SAME AS, NO NEED WRITE!\n"));
            return 0;
        }
    }

    DBGOUT(DBGFS,1,printf("Ftl: ftl_write sector %x nblocks %x\n", sector, nblocks));

    if (!(part->state & FTL_FORMATTED)) {
	DBGOUT(DBGFS,0,printf("Ftl: bad partition\n"));
	return -1;
    }

    if (((sector+nblocks-1) * SECSZ) >= part->header.FormattedSize) {
	    DBGOUT(DBGFS,0,printf("Ftl: bad write offset\n"));
	    return -1;
	}

    /* See if we need to reclaim space, before we start */

    while (part->FreeTotal < nblocks) {
	ret = reclaim_block(part);
	if (ret)
	    return ret;
    }

    bsize = 1 << part->header.EraseUnitSize;

    virt_addr = sector * SECSZ | BLOCK_DATA;
    for (i = 0; i < nblocks; i++) {

    	/* Grab a free block */
    	blk = find_free(part);
    	if (blk == 0) {
    	    static int ne = 0;
            if (++ne < 5) {
    		    DBGOUT(DBGFS,0,printf("Ftl: internal error: no free blocks!\n"));
            }
    	    return -1;
    	}

    	/* Tag the BAM entry, and write the new block */
    	log_addr = part->bam_index * bsize + blk * SECSZ;
    	part->EUNInfo[part->bam_index].Free--;
    	part->FreeTotal--;
    	if (set_bam_entry(part, log_addr, 0xfffffffe))
    	    return -1;
    	part->EUNInfo[part->bam_index].Deleted++;
    	offset = (part->EUNInfo[part->bam_index].Offset +
    		      blk * SECSZ);
    	ret = WriteFlash(part->mtd, (UInt64)offset, buffer, SECSZ, &retlen);

    	if (ret) {
    	    DBGOUT(DBGFS,0,printf("Ftl: block write failed!log_addr %x, virt_addr %x, Offset %x\n",
                log_addr, virt_addr, offset));
    	    return -1;
    	}

    	/* Only delete the old entry when the new entry is ready */
    	old_addr = part->VirtualBlockMap[sector+i];
    	if (old_addr != 0xffffffff) {
    	    part->VirtualBlockMap[sector+i] = 0xffffffff;
    	    part->EUNInfo[old_addr/bsize].Deleted++;
    	    if (set_bam_entry(part, old_addr, 0))
    		    return -1;
    	}

    	/* Finally, set up the new pointers */
    	if (set_bam_entry(part, log_addr, virt_addr))
    	    return -1;
    	part->VirtualBlockMap[sector+i] = log_addr;
    	part->EUNInfo[part->bam_index].Deleted--;

    	buffer += SECSZ;
    	virt_addr += SECSZ;
    }
    return 0;
} /* ftl_write */
//====================================================================
//====================================================================
//====================================================================
//====================================================================

extern const char LinkTarget[] = {
    0x13, 0x03, 'C', 'I', 'S'
};
extern const char DataOrg[] = {
    0x46, 0x39, 0x00, 'F', 'T', 'L', '1', '0', '0', 0x00
};

void print_header(erase_unit_header_t *hdr)
{
    DBGPRN(hdr->NumTransferUnits);
    DBGPRN(hdr->EraseCount);
    DBGPRN(hdr->LogicalEUN);
    DBGPRN(hdr->BlockSize);
    DBGPRN(hdr->EraseUnitSize);
    DBGPRN(hdr->FirstPhysicalEUN);
    DBGPRN(hdr->NumEraseUnits);
    DBGPRN(hdr->FormattedSize);
    DBGPRN(hdr->FirstVMAddress);
    DBGPRN(hdr->NumVMPages);
    DBGPRN(hdr->Flags);
    DBGPRN(hdr->Code);
    DBGPRN(hdr->SerialNumber);
    DBGPRN(hdr->AltEUHOffset);
    DBGPRN(hdr->BAMOffset);
}
//====================================================================

void print_part(partition_t *part)
{
    DBGPRN(part->size);
    DBGPRN(part->erasesize);
    DBGPRN(part->mtd);
    DBGPRN(part->state);
    DBGPRN(part->VirtualBlockMap);
    DBGPRN(part->FreeTotal);
    DBGPRN(part->EUNInfo);
    DBGPRN(part->XferInfo);
    DBGPRN(part->bam_index);
    DBGPRN(part->bam_cache);
    DBGPRN(part->DataUnits);
    DBGPRN(part->BlocksPerUnit);
    print_header(&part->header);
    DBGPRN(part->open);
}
